<?php

/**
 * bot.php - Bot
 *
 * This is a very simple class that uses CURL to perform a web request. Since
 * sessions are passed from one request to the next this class can be used for
 * simple single web page requests or complex multi-page requests. An example of
 * a simple single page request would be downloading a file. An example of a
 * complex multi-page request would be logging into an admin tool, creating a
 * new user and then downloading a report file.
 *
 * Note: This class depends on PHP5 and CURL.
 *
 * @author Toby Miller <tobius.miller@gmail.com>
 * @copyright 2009 Toby Miller <tobius.miller@gmail.com>
 * @license http://opensource.org/licenses/mit-license.php MIT License
 */

class Bot {
	/**
	 * request a web page
	 *
	 * available options
	 * [
	 *     'url' => the url being requested (query string parameters allowed)
	 *     'sessionid' => unique session id for this request
	 *     'method' => get|post
	 *     'fields' => associative array of field key/val pairs for post method
	 *     'useragent' => browser user agent string to use
	 * ]
	 *
	 * returned data
	 * [
	 *     'head' => head of returned response
	 *     'body' => body of returned response
	 *     'error' => error message if an error occurred
	 * ]
	 *
	 * @param array hash of options
	 * @return array hash of results, null on failure
	 */
	public static function request($options){
		$options = array_merge(
			array(
				'url' => null,
				'sessionid' => null,
				'method' => 'get',
				'fields' => null,
				'useragent' => 'Mozilla/5.0 (compatible; FrameworkyBot/0.1)',
				'cookies' => array()
			), $options
		);
		if ($options['sessionid'] == null){
			$options['sessionid'] = md5(uniqid(mt_rand(), true));
		}
		//$options['cookiejar'] = $GLOBALS['Frameworky']->get_config('tmp_dir') . '/master.cookie';
		//$options['cookiefile'] = $GLOBALS['Frameworky']->get_config('tmp_dir') . '/' . $options['sessionid'] . '.cookie';
		$cookies = null;
		$head = null;
		$body = null;
		$error = null;
		if ($options['url'] != null){
			$handle = curl_init();
			curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false);

			if (isset($GLOBALS['BOTCOOKIES']) && isset($GLOBALS['BOTCOOKIES'][$options['sessionid']])){
				$options['cookies'] = array_merge($options['cookies'], $GLOBALS['BOTCOOKIES'][$options['sessionid']]);
			}
			if (count($options['cookies']) > 0){
				$curl_cookies = array();
				foreach($options['cookies'] as $cookie){
					$temp = '';
					while(list($key, $val) = each($cookie)) $temp .= $key . '=' . $val . '; ';
					$curl_cookies[] = preg_replace('/; $/', '', $temp);
				}
				$curl_cookies = array_unique($curl_cookies);
				foreach($curl_cookies as $curl_cookie) curl_setopt($handle, CURLOPT_COOKIE, $curl_cookie);
			}

			curl_setopt($handle, CURLOPT_URL, $options['url']);
			if ($options['method'] == 'post' && $options['fields'] != null){
				curl_setopt($handle, CURLOPT_POST, count($options['fields']));
				$query = '';
				foreach($options['fields'] as $k => $v) $query .= $k . '=' . $v . '&';
				curl_setopt($handle, CURLOPT_POSTFIELDS, substr($query, 0, -1));
			}

			curl_setopt($handle, CURLOPT_FOLLOWLOCATION, 1);
			curl_setopt($handle, CURLOPT_MAXREDIRS, 5);
			curl_setopt($handle, CURLOPT_HEADER, 1);
			curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
			curl_setopt($handle, CURLINFO_HEADER_OUT, true);
			curl_setopt($handle, CURLOPT_USERAGENT, $options['useragent']);
			curl_setopt($handle, CURLOPT_VERBOSE, true);

			$result = curl_exec($handle);

			if ($result === false){
				$error = 'Failed to browse page';
			} else {
				$header_out = curl_getinfo($handle, CURLINFO_HEADER_OUT);
				$header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
				$head = substr($result, 0, $header_size);
				$body = substr($result, $header_size);

				// extract cookies
				$cookies = array();
				$lines = preg_split('/[\r\n]/', $head);
				$matches = array();
				foreach($lines as $line){
					if (preg_match('/Set-Cookie: /', $line)) $matches[] = preg_replace('/Set-Cookie: /', '', $line);
				}
				$matches = array_unique($matches);
				foreach($matches as $match){
					$pairs = preg_split('/\s*;\s*/', $match);
					$len = count($pairs);
					for ($i = 0; $i < $len; $i++){
						$chunk = preg_split('/=/', $pairs[$i]);
						$cookie[$chunk[0]] = $chunk[1];
					}
					$cookies[] = $cookie;
				}
				$GLOBALS['BOTCOOKIES'][$options['sessionid']] = $cookies;
			}

			curl_close($handle);
		} else {
			$error = 'URL Missing';
		}

		$return = array(
			'options' => $options,
			'cookies' => $cookies,
			'header_out' => $header_out,
			'head' => $head,
			'body' => $body,
			'error' => $error
		);

		$GLOBALS['Frameworky']->debug('Bot::request');
		$GLOBALS['Frameworky']->debug($return);
		return($return);
	}

	/**
	 * initialize this bot
	 *
	 * @param void
	 * @return void
	 */
	public function __construct(){}
}

// -- Exceptions ---------------------------------------------------------------
class BotException extends Exception {}

function read_header($ch, $string)
{
	global $location; #keep track of location/redirects
	global $cookiearr; #store cookies here
	global $ch;
	   # ^overrides the function param $ch
	   # this is okay because we need to
	   # update the global $ch with
	   # new cookies

	$length = strlen($string);
	if(!strncmp($string, "Location:", 9)){
		#keep track of last redirect
		$location = trim(substr($string, 9, -1));
	}
	if(!strncmp($string, "Set-Cookie:", 11))
	{ #get the cookie
	  $cookiestr = trim(substr($string, 11, -1));
	  $cookie = explode(';', $cookiestr);
	  $cookie = explode('=', $cookie[0]);
	  $cookiename = trim(array_shift($cookie));
	  $cookiearr[$cookiename] = trim(implode('=', $cookie));
	}
	$cookie = "";
	if(trim($string) == "")
	{  #execute only at end of header
	  foreach ($cookiearr as $key=>$value)
	  {
		$cookie .= "$key=$value; ";
	  }
	  curl_setopt($ch, CURLOPT_COOKIE, $cookie);
	}

	return $length;
}

?>
